package fr.cephb.mw4ceph;

import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.io.StringReader;
import java.io.StringWriter;
import java.net.URL;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.swing.JOptionPane;
import javax.xml.namespace.QName;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.stream.XMLInputFactory;
import javax.xml.stream.events.Attribute;
import javax.xml.stream.events.StartElement;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathFactory;

import org.apache.commons.httpclient.HttpClient;
import org.apache.commons.httpclient.HttpException;
import org.apache.commons.httpclient.cookie.CookiePolicy;
import org.apache.commons.httpclient.methods.GetMethod;
import org.apache.commons.httpclient.methods.PostMethod;
import org.apache.velocity.Template;
import org.apache.velocity.VelocityContext;
import org.apache.velocity.app.VelocityEngine;
import org.apache.velocity.runtime.resource.loader.ClasspathResourceLoader;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;



import fr.cephb.util.Cast;
import fr.cephb.xml.XML;

public class MediaWiki4Ceph
	{
	/** logger */
	protected static final Logger LOG= Logger.getLogger("fr.cephb");
	/** xml parser factory */
	protected XMLInputFactory xmlInputFactory;
	/** WP base URP */
	protected String base_api="http://odin.cng.fr/mediawiki/api.php";
	/** httpClient for loggin/wikipedia */
	protected HttpClient httpClient=new HttpClient();
	/** login */
	protected String userLogin=null;
	/** password */
	protected String userPassword=null;
	/** dom builder */
	protected DocumentBuilder documentBuilder;
	/** XPath proc */
	protected XPath xpath;
	
	/** MWAuthorization */
	protected static class MWAuthorization
		{
		String lguserid;
		String lgusername;
		String lgtoken;
		String cookieprefix;
		String sessionid;
		}
	/** revision */
	protected static class MWRevision
		{
		String title;
		String token;
		String starttimestamp;
		String basetimestamp;
		String content;
		String summary="";
		boolean nocreate=false;
		}
	
	public MediaWiki4Ceph()
		{
		xmlInputFactory = XMLInputFactory.newInstance();
		xmlInputFactory.setProperty(XMLInputFactory.IS_NAMESPACE_AWARE, Boolean.FALSE);
		xmlInputFactory.setProperty(XMLInputFactory.IS_COALESCING, Boolean.TRUE);
		xmlInputFactory.setProperty(XMLInputFactory.IS_REPLACING_ENTITY_REFERENCES, Boolean.TRUE);
		
		DocumentBuilderFactory domFactory= DocumentBuilderFactory.newInstance();
		domFactory.setCoalescing(true);
		domFactory.setExpandEntityReferences(true);
		domFactory.setIgnoringComments(true);
		domFactory.setNamespaceAware(true);
		domFactory.setValidating(false);
		domFactory.setIgnoringElementContentWhitespace(true);
		try
			{
			this.documentBuilder= domFactory.newDocumentBuilder();
			}
		catch (ParserConfigurationException e)
			{
			throw new RuntimeException(e);
			}
		
		XPathFactory xpathFactory=XPathFactory.newInstance();
		this.xpath=xpathFactory.newXPath();
		
				
		}
	
	/** escapes WP title */
	protected String escape(String entry) throws IOException
		{
		return entry.replace(' ', '_');
		}
	
	protected String attr(StartElement e, String attName)
		{
		Attribute att=e.getAttributeByName(new QName(attName));
		return att==null?"":att.getValue();
		}
	
	/**
	 * Open a URL to the given stream, retry 10 times if it fails
	 * @param url
	 * @return
	 * @throws IOException
	 */
	protected InputStream openStream(String url) throws IOException
		{
		LOG.info("opening "+url);
		final int tryNumber=10;
		IOException lastError=null;
		URL net = new URL(url);
		for(int i=0;i< tryNumber;++i)
			{
			try
				{
				InputStream in=net.openStream();
				return in;
				}
			catch(IOException err)
				{
				lastError=err;
				LOG.info("Trying "+i+" "+err.getMessage());
				try {
					Thread.sleep(10000);//sleep 10secs
				} catch (Exception e) {
					
				}
				continue;
				}
			}
		throw lastError;
		}
	
	
	protected MWAuthorization login() throws IOException,SAXException
		{
		MWAuthorization authorization= null;
		PostMethod postMethod=null;
		
			try
				{
				LOG.info("logging as "+this.userLogin);
				postMethod = new PostMethod(
						"http://en.wikipedia.org/w/api.php"
						);
				
				postMethod.addParameter("action", "login");
				postMethod.addParameter("format", "xml");
				postMethod.addParameter("lgname", this.userLogin);
				postMethod.addParameter("lgpassword",this.userPassword);
				postMethod.getParams().setCookiePolicy(CookiePolicy.RFC_2109);
				
				
				int status = this.httpClient.executeMethod(postMethod);
				if(status==200)
					{
					Document dom= documentBuilder.parse(  postMethod.getResponseBodyAsStream());
					Element e= dom.getDocumentElement();
					if(e==null || !e.getTagName().equals("api")) throw new IOException("not api");
					e= XML.firstElement(e);
					if(e==null || !e.getTagName().equals("login")) throw new IOException("not login");
					if(!"Success".equals(e.getAttribute("result"))) throw new IOException("not login@result");
					authorization= new MWAuthorization();
					authorization.cookieprefix= e.getAttribute("cookieprefix");
					authorization.lgusername= e.getAttribute("lgusername");
					authorization.lgtoken= e.getAttribute("lgtoken");
					authorization.sessionid= e.getAttribute("sessionid");
					authorization.lguserid= e.getAttribute("lguserid");
					
					}
				else
					{
					throw new IOException("bad http status:"+status);
					}
				
				
				return authorization;
				}
			catch(HttpException  err)
				{
				err.printStackTrace();
				throw err;
				}
			catch(IOException err)
				{
				err.printStackTrace();
				throw err;
				}
			catch(Throwable err)
				{
				err.printStackTrace();
				throw new RuntimeException(err);
				}
			finally
				{
				if(postMethod!=null) postMethod.releaseConnection();
				}
			}

protected String resolveCaptcha(String question)
	{
	if(question==null) return null;
	String tokens[]=question.trim().replaceAll("[ \t]+", " ").split("[ ]");
	if(tokens.length!=3) return null;
	
	Integer left= Cast.Integer.cast(tokens[0]);
	if(left==null) return null;
	Integer right= Cast.Integer.cast(tokens[2]);
	if(right==null) return null;
	if(tokens[1].equals("+"))
		{
		return String.valueOf(left+right);
		}
	else if(tokens[1].equals("-"))
		{
		return String.valueOf(left-right);
		}
	else if(tokens[1].equals("*"))
		{
		return String.valueOf(left*right);
		}
	else if(tokens[1].equals("/") && right!=0)
		{
		return String.valueOf(left/right);
		}
	else
		{
		System.err.println("Cannot result "+question);
		return null;
		}
	}

protected MWRevision getRevision(String page)
 	throws IOException,SAXException
	{
	GetMethod getMethod= null;

	try
		{
		String url=
			this.base_api+"?action=query" +
			"&format=xml&intoken=edit" +
			"&prop="+URLEncoder.encode("info|revisions","UTF-8") +
			"&titles="+escape(page)+
			"&rvprop="+URLEncoder.encode("timestamp|content|revisions","UTF-8")
			;
		LOG.info(url);
		getMethod= new GetMethod(url);
		
		
		int status = this.httpClient.executeMethod(getMethod);
		if(status!=200)
			{
			LOG.warning("Cannot send get method ");
			return null;
			}
		InputStream in= getMethod.getResponseBodyAsStream();
		Document dom= documentBuilder.parse(in);
		in.close();
		Element api=dom.getDocumentElement();
		if(api==null) throw new IOException("no root");
		Element queryTag = XML.firstElement(api, "query");
		if(queryTag==null) throw new IOException("no query");
		Element pages = XML.firstElement(queryTag, "pages");
		if(pages==null) throw new IOException("no pages");
		Element pageTag = XML.firstElement(pages, "page");
		if(pageTag==null) throw new IOException("no page");
		
		
		
		MWRevision revision= new MWRevision();
		
		revision.title=page;
		revision.token= pageTag.getAttribute("edittoken");
		revision.starttimestamp=pageTag.getAttribute("starttimestamp");
		Element revisions=  XML.firstElement(pageTag, "revisions");
		if(revisions==null)
			{
			LOG.info("no revision for "+page);
			return null;
			}
		Element rev=  XML.firstElement(revisions, "rev");
		if(rev==null) throw new IOException("no rev");
		revision.basetimestamp=rev.getAttribute("timestamp");
		revision.content=rev.getTextContent();
		
		return revision;
		}
	catch(HttpException  err)
		{
		throw err;
		}
	catch(IOException err)
		{
		throw err;
		}
	finally
		{
		if(getMethod!=null) getMethod.releaseConnection();
		}
	}

protected boolean update(
		MWRevision revision,
		MWAuthorization authorization
		) throws IOException,SAXException
		{
		PostMethod postMethod=null;
	
	
		try
			{
	
			
			String captchaid=null;
			String captchaword=null;
			int tryCount=0;
			while((tryCount++)<10)
				{
				postMethod = new PostMethod(
						this.base_api
						);
				postMethod.setRequestHeader( "Content-Type", "application/x-www-form-urlencoded; charset=utf-8");
	
				//postMethod.addParameter("bot", "true");
				postMethod.addParameter("action","edit");
				postMethod.addParameter("title",revision.title.replace(' ', '_'));
				postMethod.addParameter("summary",revision.summary);
				postMethod.addParameter("text",revision.content);
				postMethod.addParameter("basetimestamp",revision.basetimestamp);
				postMethod.addParameter("starttimestamp",revision.starttimestamp);
				postMethod.addParameter("token",revision.token);
				postMethod.addParameter("notminor","");
				postMethod.addParameter("format","xml");
				if(revision.nocreate)
					{
					postMethod.addParameter("nocreate","true");
					}
				
				if(captchaid!=null && captchaword!=null)
					{
					LOG.info("Setting captchaid "+captchaid +"="+captchaword);
					postMethod.addParameter("captchaid", captchaid);
					postMethod.addParameter("captchaword", captchaword);
					}
				
				int status = this.httpClient.executeMethod(postMethod);
				if(status==200)
					{
					String response= postMethod.getResponseBodyAsString();
					Document dom= this.documentBuilder.parse(new InputSource(new StringReader(response)));
					Element api=dom.getDocumentElement();
					if(api==null) throw new IOException("no root");
					Element editTag = XML.firstElement(api, "edit");
					if(editTag==null) throw new IOException("no edit");
					String result= editTag.getAttribute("result");
					if(result.equals("Failure"))
						{
						Element captcha= XML.firstElement(editTag, "captcha");
						if(captcha==null)
							{
							LOG.warning("No captcha for "+response);
							return false;
							}
						captchaid= captcha.getAttribute("id");
						//String type= captcha.getAttribute("type");
						String question=captcha.getAttribute("question");
						
						captchaword = resolveCaptcha(question);
						if(captchaword==null)
							{
							captchaword= JOptionPane.showInputDialog(null,
									""+question,revision.title+" "+tryCount+" "+response,
									JOptionPane.QUESTION_MESSAGE
									);
							}
						if(captchaword==null) captchaword="";
						postMethod.releaseConnection();
						continue;
						}
					
					System.out.println("Done: "+revision.title +" "+response);
					return true;
					}
				else
					{
					throw new IOException("bad http status:"+status);
					}
				}
			System.out.println("Error trying mutiple time for: "+revision.title);
			return false;
			}
		catch(HttpException  err)
			{
			throw err;
			}
		catch(IOException err)
			{
			throw err;
			}
		finally
			{
			if(postMethod!=null) postMethod.releaseConnection();
			}
		}
	
	public class StdValue
		{
		private double mean;
		private double stdev;
		public StdValue(double mean,double stdev)
			{
			this.mean=mean;
			this.stdev=stdev;
			}
		
		public double getMean()
			{
			return mean;
			}
		
		public double getStdev()
			{
			return stdev;
			}
		
		@Override
		public String toString()
			{
			return String.valueOf((int)this.mean)+" +/- "+(int)this.stdev;
			}
		}
	
	public class LaneSummary
		{
		String laneYield;
		StdValue clusterCountRaw;
		StdValue clusterCountPF;
		StdValue oneSig;
		StdValue signal20AsPctOf1;
		StdValue percentClustersPF;
		StdValue percentUniquelyAlignedPF;
		StdValue averageAlignScorePF;
		StdValue errorPF;
		
		public String getLaneYield()
			{
			return laneYield;
			}
		public StdValue getClusterCountRaw()
			{
			return clusterCountRaw;
			}
		
		public StdValue getClusterCountPF()
			{
			return clusterCountPF;
			}
		public StdValue getOneSig()
			{
			return oneSig;
			}
		public StdValue getSignal20AsPctOf1()
			{
			return signal20AsPctOf1;
			}
		
		public StdValue getPercentUniquelyAlignedPF()
			{
			return percentUniquelyAlignedPF;
			}
	
		public StdValue getPercentClustersPF()
			{
			return percentClustersPF;
			}
		public StdValue getAverageAlignScorePF()
			{
			return averageAlignScorePF;
			}
		public StdValue getErrorPF()
			{
			return errorPF;
			}
		}
	
	
	public class Lane
		{
		int index;
		String sampleType;
		String sampleID;
		String concentration;
		String species;
		String analysisType;
		String comment;	
		List<LaneSummary> summaries= new ArrayList<LaneSummary>();
		String type;
		
		public List<LaneSummary> getSummaries()
			{
			return summaries;
			}
		
		public String getType()
			{
			return this.type;
			}
		
		public int getIndex()
			{
			return index;
			}
		public String getSampleType()
			{
			return sampleType;
			}
		public String getSampleID()
			{
			return sampleID;
			}
		public String getConcentration()
			{
			return concentration;
			}
		public String getSpecies()
			{
			return species;
			}
		public String getAnalysisType()
			{
			return analysisType;
			}
		public String getComment()
			{
			return comment;
			}
		
		
		}

	public class RunFolder
		{
		public File directory;
		public String year;
		public String month;
		public String day;
		public String machineName;
		public String flowcellID;
		
		private String operator="N/A";
		public String flowCellType;
		public String comment="N/A";
		public int nbCyclesSequencing;
		public File summaryFile;
		
		List<Lane> lanes= new ArrayList<Lane>();
		
		public File getDirectory()
			{
			return directory;
			}
		
		public String getYear()
			{
			return year;
			}
		
		public String getMonth()
			{
			return month;
			}
		public String getDay()
			{
			return day;
			}
		
		public String getMachineName()
			{
			return machineName;
			}
		public String getFlowcellID()
			{
			return flowcellID;
			}
		
		public String getFlowCellType()
			{
			return flowCellType;
			}
		
		public String getComment()
			{
			return comment;
			}
		
		public Set<String> getSpecies()
			{
			Set<String> set= new HashSet<String>();
			for(Lane L:getLanes())
				{
				if(L.species==null || L.species.length()==0) continue;
				set.add(L.species);
				}
			return set;
			}
		
		public Set<String> getTypes()
			{
			Set<String> set= new HashSet<String>();
			for(Lane L:getLanes())
				{
				if(L.getType()==null || L.getType().length()==0) continue;
				set.add(L.getType());
				}
			return set;
			}
		
		public int getNumberOfCycles()
			{
			return nbCyclesSequencing;
			}
		
		 int getMaxReadCount()
			{
			int n=0;
			for(Lane L:this.getLanes())
				{
				n=Math.max(n, L.getSummaries().size());
				}
			return n;
			}
		
		public boolean getAnalysed()
			{
			return getMaxReadCount()>0;
			}
		
		public File getAnalysisDirectory()
			{
			return summaryFile.getParentFile();
			}
		
		public String getDateOfAnalysis()
			{
			return new java.util.Date(summaryFile.lastModified()).toString();
			}
		
		
		public String getOperator()
			{
			return operator;
			}
		
		
		
		public List<Lane> getLanes()
			{
			return lanes;
			}
		
		public int getGaName()
			{
			if(machineName.endsWith("337")) return 2;
			if(machineName.endsWith("341")) return 3;
			if(machineName.endsWith("371")) return 4;
			if(machineName.endsWith("539")) return 5;
			if(machineName.endsWith("550")) return 6;
			if(machineName.endsWith("665")) return 7;
			if(machineName.endsWith("671")) return 8;
			if(machineName.endsWith("675")) return 9;
			if(machineName.endsWith("49")) return 1;
			
			throw new IllegalArgumentException("illegal machine name:"+machineName);
			}
		}

	public static class RunFolderSet
		{
		private List<RunFolder> runs= new ArrayList<RunFolder>();
		public List<RunFolder> getRuns()
			{
			return this.runs;
			}
		public int getMaxReadCount()
			{
			int n=0;
			for(RunFolder run:getRuns())
				{
				n=Math.max(n,run.getMaxReadCount());
				}
			return n;
			}
		}
	
	
	private StdValue parseStdValue(Node root,String name)
		{
		Element e= XML.firstElement(root, name);
		if(e==null) return null;
		return new StdValue(
			Double.parseDouble(XML.firstElement(e,"mean").getTextContent()),
			Double.parseDouble(XML.firstElement(e,"stdev").getTextContent())		
			);
		}
	
	/** cf pipeline1.4_user_guide.pdf p. 25 */
	private RunFolder processRunFolder(File runFolder)
		throws Exception
		{
		RunFolder run=new RunFolder();
		run.directory=runFolder;
		if(!runFolder.isDirectory())
			{
			System.err.println("#Error: not a directory "+runFolder);
			return null;
			}
		final Pattern runFolderRegex=Pattern.compile(
			"(\\d{2,5})(\\d\\d)(\\d\\d)_([^_]+)_(\\d\\d\\d\\d)_([^_]+)");
		Matcher matcher=runFolderRegex.matcher(runFolder.getName());
		if(!matcher.matches())
			{
			System.err.println("#Error: not a run folder. Doesn't match "+runFolderRegex.pattern()+" : "+runFolder);
			return null;
			}
		run.year= matcher.group(1);
		run.month= matcher.group(2);
		run.day= matcher.group(3);
		run.machineName=matcher.group(4);
		run.flowcellID=matcher.group(6);
		File xmlFile=new File(runFolder,run.flowcellID+".xml");
		if(!xmlFile.exists())
			{
			System.err.println("#Error: sample sheet "+xmlFile+" missing in "+runFolder);
			return null;
			}
		Document sampleSheetDoc= documentBuilder.parse(xmlFile);
		Element root= sampleSheetDoc.getDocumentElement();
		assert(root.getNodeName().equals("SampleInfo"));
		for(Node n=root.getFirstChild();n!=null;n=n.getNextSibling())
			{
			if(n.getNodeType()!=Node.ELEMENT_NODE) continue;
			Element e=Element.class.cast(n);
			String name=e.getNodeName();
			if(name.equals("FlowcellID"))
				{
				assert(e.getTextContent().equals(run.flowcellID));
				}
			else if(name.equals("Comment"))
				{
				run.comment=e.getTextContent();;
				}
			else if(name.equals("Operator"))
				{
				run.operator=e.getTextContent();;
				}
			else if(name.equals("FlowCellType"))
				{
				run.flowCellType=e.getTextContent();;
				}
			else if(name.equals("NbCyclesSequencing"))
				{
				run.nbCyclesSequencing=Integer.parseInt(e.getTextContent());
				}
			else if(name.matches("Lane\\d+"))
				{
				Lane lane= new Lane();
				lane.index=Integer.parseInt(name.substring(4));
				run.lanes.add(lane);
				for(Node n2=e.getFirstChild();n2!=null;n2=n2.getNextSibling())
					{
					if(n2.getNodeType()!=Node.ELEMENT_NODE) continue;
					Element e2=Element.class.cast(n2);
					name=e2.getTagName();
					if(name.equals("SampleType"))
						{
						lane.sampleType=e2.getTextContent();
						}
					else if(name.equals("SampleID"))
						{
						lane.sampleID=e2.getTextContent();
						}
					else if(name.equals("Concentration"))
						{
						lane.concentration=e2.getTextContent();
						}
					else if(name.equals("Species"))
						{
						lane.species=e2.getTextContent();
						}
					else if(name.equals("AnalysisType"))
						{
						lane.analysisType=e2.getTextContent();
						}
					else if(name.equals("Comment"))
						{
						lane.comment=e2.getTextContent();
						}
					else{
						LOG.info("unknown tag in Lane: "+name);
						}
					}
				}
			else{
				LOG.info("unknown tag "+name);
				}
			}
		File dataDir=new File(runFolder,"Data");
		if(!dataDir.exists())
			{
			System.err.println("Data dir does not exist "+runFolder);
			return null;
			}
		if(!dataDir.isDirectory())
			{
			System.err.println("Data is not a directory "+runFolder);
			return null;
			}
		run.summaryFile=findFile(dataDir,"Summary.xml");
		if(run.summaryFile==null)
			{
			System.err.println("Cannot find Summary.xml under  "+dataDir);
			return null;
			}
		Document summary= documentBuilder.parse(run.summaryFile);
		
		for(Lane L: run.getLanes())
			{
			Node n1=(Node)this.xpath.evaluate(
					"/Summary/LaneParameterSummary/Lane[laneNumber='"+L.index+"']",
					summary, XPathConstants.NODE);
			if(n1==null)
				{
				System.err.println("Cannot get LaneParameterSummary for "+L.index);
				continue;
				}
			L.type = XML.firstElement(n1, "type").getTextContent();
			}
		
		for(int side=0;side<2;++side)
			{
			for(Lane L: run.getLanes())
				{
				Node n1=(Node)this.xpath.evaluate(
						"/Summary/LaneResultsSummary/Read[readNumber='"+(side+1)+"']/Lane[laneNumber='"+L.index+"']",
						summary, XPathConstants.NODE);
				if(n1==null)
					{
					if(side==0) System.err.println("Cannot get node");
					continue;
					}
				LaneSummary laneSummary=new LaneSummary();
				L.summaries.add(laneSummary);
				laneSummary.laneYield=(String)this.xpath.evaluate("laneYield",n1,XPathConstants.STRING);
				laneSummary.clusterCountRaw= parseStdValue(n1, "clusterCountRaw");
				laneSummary.clusterCountPF= parseStdValue(n1, "clusterCountPF");
				laneSummary.oneSig= parseStdValue(n1, "oneSig");
				laneSummary.signal20AsPctOf1=parseStdValue(n1, "signal20AsPctOf1");
				laneSummary.percentClustersPF=parseStdValue(n1, "percentClustersPF");
				laneSummary.percentUniquelyAlignedPF= parseStdValue(n1, "percentUniquelyAlignedPF");
				laneSummary.averageAlignScorePF= parseStdValue(n1, "averageAlignScorePF");
				laneSummary.errorPF= parseStdValue(n1, "errorPF");
				}
			}
		return run;
		}
	
	
	
	private File findFile(File dir,String filename)
		throws IOException
		{
		File content[]=dir.listFiles();
		File rez=null;
		for(File f:content)
			{
			if(!f.isFile()) continue;
			if(f.getName().equals(filename))
				{
				rez=f;
				}
			}
		for(File f:content)
			{
			if(!f.isDirectory()) continue;
			File rez2= findFile(f, filename);
			if(rez2!=null )
				{
				
				if(rez!=null)
					{
					if( rez.lastModified() <rez2.lastModified())
						{
						System.err.println("using newest: "+rez2+" instead of "+rez);
						rez=rez2;
						}
					}
				else
					{
					rez=rez2;
					}
				}
			}
		return rez;
		}
	
	
	
	private String createWikiText(RunFolderSet runs)
		throws Exception
		{
		VelocityContext ctx=new VelocityContext();
		

		ctx.put("runs", runs);
		StringWriter sw= new StringWriter();
		PrintWriter out= new PrintWriter(sw);
		merge(ctx,out,"/META-INF/runs2wiki.vm");
		out.flush();
		out.close();
		return sw.toString();
		}
	
	private String createHTMLText(RunFolderSet runs)
	throws Exception
		{
		VelocityContext ctx=new VelocityContext();
		ctx.put("runs", runs);
		StringWriter sw= new StringWriter();
		PrintWriter out= new PrintWriter(sw);
		merge(ctx,out,"/META-INF/runs2xhtml.vm");
		out.flush();
		out.close();
		return sw.toString();
		}
	
	
	private void merge(VelocityContext ctx,PrintWriter out,String templateName)
		throws Exception
		{
		VelocityEngine engine;
		engine = new VelocityEngine();
		engine.setProperty("runtime.log.logsystem.class", 
		"org.apache.velocity.runtime.log.NullLogSystem");
		
		engine.setProperty(VelocityEngine.RESOURCE_LOADER, "class");
		engine.setProperty(
	            "class." + VelocityEngine.RESOURCE_LOADER + ".class",
	            ClasspathResourceLoader.class.getName()
	            );
		
	
		Template template =   engine.getTemplate(templateName);
		
		template.merge(ctx, out);
		out.flush();
		}
	
	public static void main(String[] args)
		{
		LOG.setLevel(Level.OFF);
		String mode="html";
		MediaWiki4Ceph app= new MediaWiki4Ceph();
		try
			{
			int optind=0;
			while(optind< args.length)
				{
				if(args[optind].equals("-h") ||
				   args[optind].equals("-help") ||
				   args[optind].equals("--help"))
					{
					System.err.println("Options:");
					System.err.println(" -h help; This screen.");
					System.err.println(" -log-level <java.util.loggin.Level> default:"+LOG.getLevel());
					System.err.println(" -api <base-api> default:"+app.base_api);
					System.err.println(" -m <s> mode : 'html' or 'wiki' default:html");
					return;
					}
				else if(args[optind].equals("-log-level"))
	                 {
	                 Level L=Level.parse(args[++optind]);
	                 if(L==null) throw new IllegalArgumentException("Bad level:"+args[optind]);
	                 LOG.setLevel(L);
	                 }
				else if(args[optind].equals("-api"))
					{
					app.base_api=args[++optind];
					}
				else if(args[optind].equals("-m"))
					{
					mode=args[++optind];
					}
				else if(args[optind].equals("-user"))
					{
					app.userLogin=args[++optind];
					}
				else if(args[optind].equals("-password"))
					{
					app.userPassword=args[++optind];
					}
				else if(args[optind].equals("--"))
					{
					optind++;
					break;
					}
				else if(args[optind].startsWith("-"))
					{
					System.err.println("Unknown option "+args[optind]);
					return;
					}
				else 
					{
					break;
					}
				++optind;
				}
			RunFolderSet runs=new RunFolderSet();
			if(optind==args.length)
				{
				LOG.info("read run folders stdin");
				String line;
				BufferedReader in= new BufferedReader(new InputStreamReader(System.in));
				while((line=in.readLine())!=null)
					{
					if(line.trim().length()==0) continue;
					if(line.startsWith("#")) continue;
					File file= new File(line.trim());
					RunFolder run=app.processRunFolder(file);
					if(run!=null) runs.runs.add(run);
					}
				}
			else
				{
				while(optind< args.length)
					{
					String fname=args[optind++];
					File file= new File(fname);
					RunFolder run=app.processRunFolder(file);
					if(run!=null) runs.runs.add(run);
					}
				}
			String content;
			if(mode.equals("wiki"))
				{
				content=app.createWikiText(runs);
				}
			else if(mode.equals("html"))
				{
				content=app.createHTMLText(runs);
				}
			else
				{
				System.err.println("Unknown mode "+mode);
				return;
				}
			System.out.println(content);
			} 
		catch(Throwable err)
			{
			err.printStackTrace();
			}
		}
	}
